<template>
  <CommonDrawer
    :show="show"
    :title="title"
    :loading="loading"
    @show="resetState"
    @close="$emit('close')"
  >
    <template #default>
      <div class="flex flex-col gap-y-4 h-full px-1">
        <!-- Error Alert -->
        <NAlert
          v-if="validationErrors.length > 0"
          type="error"
          :title="$t('rollout.task-execution-errors')"
        >
          <ul class="list-disc list-inside flex flex-col gap-y-1">
            <li
              v-for="(error, index) in validationErrors"
              :key="index"
              class="text-sm"
            >
              {{ error }}
            </li>
          </ul>
        </NAlert>

        <!-- Warning Alert -->
        <NAlert
          v-if="validationWarnings.length > 0"
          type="warning"
          :title="$t('rollout.task-execution-notices')"
        >
          <ul class="list-disc list-inside flex flex-col gap-y-1">
            <li
              v-for="(warning, index) in validationWarnings"
              :key="index"
              class="text-sm"
            >
              {{ warning }}
            </li>
          </ul>
        </NAlert>

        <!-- Plan Check Status -->
        <div v-if="planCheckStatus.total > 0" class="flex items-center gap-3">
          <span class="font-medium text-control shrink-0">{{
            $t("plan.navigator.checks")
          }}</span>
          <PlanCheckStatusCount :plan="plan" />
        </div>

        <!-- Stage information -->
        <div
          v-if="shouldShowStageInfo"
          class="flex flex-row gap-x-2 shrink-0 overflow-y-hidden justify-start items-center"
        >
          <label class="font-medium text-control">
            {{ $t("common.stage") }}
          </label>
          <EnvironmentV1Name
            :environment="
              environmentStore.getEnvironmentByName(targetStage.environment)
            "
            :link="false"
          />
        </div>

        <!-- Task information -->
        <div v-if="shouldShowTaskInfo" class="flex flex-col gap-y-1 shrink-0">
          <div class="flex items-center justify-between">
            <label class="text-control">
              <span class="font-medium">{{
                $t("common.task", eligibleTasks.length)
              }}</span>
              <span
                class="opacity-80"
                v-if="eligibleTasks.length > 1 && shouldShowStageInfo"
              >
                ({{
                  eligibleTasks.length === target.stage.tasks.length
                    ? eligibleTasks.length
                    : `${eligibleTasks.length} / ${target.stage.tasks.length}`
                }})
              </span>
            </label>
          </div>
          <div>
            <template v-if="useVirtualScroll">
              <NVirtualList
                :items="eligibleTasks"
                :item-size="itemHeight"
                class="max-h-64"
                item-resizable
              >
                <template #default="{ item: task }">
                  <div
                    :key="task.name"
                    class="flex items-center text-sm gap-2"
                    :style="{ height: `${itemHeight}px` }"
                  >
                    <TaskStatus
                      :status="task.status"
                      size="small"
                    />
                    <TaskDatabaseName :task="task" />
                  </div>
                </template>
              </NVirtualList>
            </template>
            <template v-else>
              <NScrollbar class="max-h-64">
                <ul class="text-sm flex flex-col gap-y-2">
                  <li
                    v-for="task in eligibleTasks"
                    :key="task.name"
                    class="flex items-center gap-2"
                  >
                    <TaskStatus
                      :status="task.status"
                      size="small"
                    />
                    <TaskDatabaseName :task="task" />
                  </li>
                </ul>
              </NScrollbar>
            </template>
          </div>
        </div>

        <div v-if="showScheduledTimePicker" class="flex flex-col">
          <h3 class="font-medium text-control mb-1">
            {{ $t("task.execution-time") }}
          </h3>
          <NRadioGroup
            :size="'large'"
            :value="runTimeInMS === undefined ? 'immediate' : 'scheduled'"
            @update:value="handleExecutionModeChange"
            class="flex! flex-row gap-x-4"
          >
            <!-- Run Immediately Option -->
            <NRadio value="immediate">
              <span>{{ $t("task.run-immediately.self") }}</span>
            </NRadio>

            <!-- Schedule for Later Option -->
            <NRadio value="scheduled">
              <span>{{ $t("task.schedule-for-later.self") }}</span>
            </NRadio>
          </NRadioGroup>

          <!-- Description based on selection -->
          <div class="mt-1 text-sm text-control-light">
            <span v-if="runTimeInMS === undefined">
              {{ $t("task.run-immediately.description") }}
            </span>
            <span v-else>
              {{ $t("task.schedule-for-later.description") }}
            </span>
          </div>

          <!-- Scheduled Time Options -->
          <div v-if="runTimeInMS !== undefined" class="flex flex-col">
            <NDatePicker
              v-model:value="runTimeInMS"
              type="datetime"
              :placeholder="$t('task.select-scheduled-time')"
              :is-date-disabled="
                (date: number) => date < dayjs().startOf('day').valueOf()
              "
              format="yyyy-MM-dd HH:mm:ss"
              :actions="['clear', 'confirm']"
              clearable
              class="mt-2"
            />
          </div>
        </div>

        <!-- Only show reason/comment input if issue is available -->
        <div
          v-if="shouldShowComment"
          class="flex flex-col gap-y-1 shrink-0 border-t pt-2"
        >
          <p class="text-control">
            {{ $t("common.comment") }}
          </p>
          <NInput
            v-model:value="comment"
            type="textarea"
            :placeholder="$t('issue.leave-a-comment')"
            :autosize="{
              minRows: 3,
              maxRows: 10,
            }"
            :maxlength="1000"
          />
        </div>
      </div>
    </template>
    <template #footer>
      <div class="w-full flex flex-row justify-between items-center gap-x-2">
        <!-- Bypass stage requirements checkbox -->
        <div v-if="shouldShowBypassOption" class="flex items-center">
          <NCheckbox v-model:checked="bypassPolicyChecks" :disabled="loading">
            {{ $t("rollout.bypass-stage-requirements") }}
          </NCheckbox>
        </div>
        <div v-else />

        <div class="flex justify-end gap-x-2">
          <NButton quaternary @click="$emit('close')">
            {{ $t("common.close") }}
          </NButton>

          <NTooltip :disabled="validationErrors.length === 0" placement="top">
            <template #trigger>
              <NButton
                :disabled="
                  validationErrors.length > 0 ||
                  (validationWarnings.length > 0 && !bypassPolicyChecks)
                "
                type="primary"
                @click="handleConfirm"
              >
                {{ title }}
              </NButton>
            </template>
            <template #default>
              <ErrorList :errors="validationErrors" />
            </template>
          </NTooltip>
        </div>
      </div>
    </template>
  </CommonDrawer>
</template>

<script setup lang="ts">
import { create } from "@bufbuild/protobuf";
import { TimestampSchema } from "@bufbuild/protobuf/wkt";
import dayjs from "dayjs";
import { flatten } from "lodash-es";
import {
  NAlert,
  NButton,
  NCheckbox,
  NDatePicker,
  NInput,
  NRadio,
  NRadioGroup,
  NScrollbar,
  NTooltip,
  NVirtualList,
} from "naive-ui";
import { computed, ref, watchEffect } from "vue";
import { useI18n } from "vue-i18n";
import { ErrorList } from "@/components/IssueV1/components/common";
import CommonDrawer from "@/components/IssueV1/components/Panel/CommonDrawer.vue";
import TaskStatus from "@/components/Rollout/kits/TaskStatus.vue";
import { EnvironmentV1Name } from "@/components/v2";
import { rolloutServiceClientConnect } from "@/grpcweb";
import {
  pushNotification,
  useCurrentUserV1,
  useEnvironmentV1Store,
  usePolicyByParentAndType,
} from "@/store";
import {
  getProjectIdRolloutUidStageUidTaskUid,
  projectNamePrefix,
  rolloutNamePrefix,
  stageNamePrefix,
  userNamePrefix,
} from "@/store/modules/v1/common";
import {
  Issue_ApprovalStatus,
  Issue_Approver_Status,
  Issue_Type,
} from "@/types/proto-es/v1/issue_service_pb";
import {
  PolicyType,
  RolloutPolicy_Checkers_PlanCheckEnforcement,
} from "@/types/proto-es/v1/org_policy_service_pb";
import type {
  BatchRunTasksRequest,
  Stage,
  Task,
} from "@/types/proto-es/v1/rollout_service_pb";
import {
  BatchCancelTaskRunsRequestSchema,
  BatchRunTasksRequestSchema,
  BatchSkipTasksRequestSchema,
  ListTaskRunsRequestSchema,
  Task_Status,
  Task_Type,
  TaskRun_Status,
} from "@/types/proto-es/v1/rollout_service_pb";
import { extractStageUID, isNullOrUndefined } from "@/utils";
import { usePlanCheckStatus, usePlanContextWithRollout } from "../../logic";
import PlanCheckStatusCount from "../PlanCheckStatusCount.vue";
import TaskDatabaseName from "./TaskDatabaseName.vue";
import { canRolloutTasks } from "./taskPermissions";

// Default delay for running tasks if not scheduled immediately.
// 1 hour in milliseconds
const DEFAULT_RUN_DELAY_MS = 60 * 60 * 1000;

export type TargetType = { type: "tasks"; tasks?: Task[]; stage: Stage };

const props = defineProps<{
  show: boolean;
  action: "RUN" | "SKIP" | "CANCEL";
  target: TargetType;
}>();

const emit = defineEmits<{
  (event: "close"): void;
  (event: "confirm"): void;
}>();

const { t } = useI18n();
const { issue, rollout, plan, taskRuns } = usePlanContextWithRollout();
const currentUser = useCurrentUserV1();

const loading = ref(false);
const environmentStore = useEnvironmentV1Store();
const comment = ref("");
const runTimeInMS = ref<number | undefined>(undefined);
const bypassPolicyChecks = ref(false);
const { statusSummary: planCheckStatus } = usePlanCheckStatus(plan);

// Check issue approval status using the review context
const issueApprovalStatus = computed(() => {
  if (!issue?.value) {
    return { rolloutReady: true, hasIssue: false };
  }

  const currentIssue = issue.value;
  const approvalTemplate = currentIssue.approvalTemplate;

  // Check if issue has approval template
  if (!approvalTemplate || (approvalTemplate.flow?.roles || []).length === 0) {
    return { rolloutReady: true, hasIssue: true };
  }

  // Use the approval status to determine if rollout is ready
  const rolloutReady =
    currentIssue.approvalStatus === Issue_ApprovalStatus.APPROVED ||
    currentIssue.approvalStatus === Issue_ApprovalStatus.SKIPPED;

  // Determine the specific status (rejected vs pending)
  let status = "pending";
  if (
    currentIssue.approvers.some(
      (app) => app.status === Issue_Approver_Status.REJECTED
    )
  ) {
    status = "rejected";
  }

  return {
    rolloutReady,
    hasIssue: true,
    status,
  };
});

const shouldShowComment = computed(() => !isNullOrUndefined(issue?.value));

// Plan check error validation based on rollout policy checkers
const planCheckError = computed(() => {
  if (props.action !== "RUN") {
    return undefined;
  }

  // Get the plan check enforcement level from the rollout policy
  const planCheckEnforcement =
    rolloutPolicy.value?.policy?.case === "rolloutPolicy"
      ? rolloutPolicy.value.policy.value.checkers?.requiredStatusChecks
          ?.planCheckEnforcement
      : undefined;
  // If no enforcement is specified, default to no validation
  if (!planCheckEnforcement) {
    return undefined;
  }

  if (planCheckStatus.value.running > 0) {
    return t(
      "custom-approval.issue-review.disallow-approve-reason.some-task-checks-are-still-running"
    );
  }
  // ERROR_ONLY enforcement: only block on errors
  if (
    planCheckEnforcement ===
    RolloutPolicy_Checkers_PlanCheckEnforcement.ERROR_ONLY
  ) {
    if (planCheckStatus.value.error > 0) {
      return t(
        "custom-approval.issue-review.disallow-approve-reason.some-task-checks-didnt-pass"
      );
    }
  }
  // STRICT enforcement: block on both errors and warnings
  if (
    planCheckEnforcement === RolloutPolicy_Checkers_PlanCheckEnforcement.STRICT
  ) {
    if (planCheckStatus.value.error > 0 || planCheckStatus.value.warning > 0) {
      return t(
        "custom-approval.issue-review.disallow-approve-reason.some-task-checks-didnt-pass"
      );
    }
  }
  return undefined;
});

// Plan check warning validation for non-blocking plan check results
const planCheckWarning = computed(() => {
  if (props.action !== "RUN" || planCheckStatus.value.total === 0) {
    return undefined;
  }

  // Get the plan check enforcement level from the rollout policy
  const planCheckEnforcement =
    rolloutPolicy.value?.policy?.case === "rolloutPolicy"
      ? rolloutPolicy.value.policy.value.checkers?.requiredStatusChecks
          ?.planCheckEnforcement
      : undefined;

  // If there's no enforcement policy, show any plan check issues as warnings
  if (!planCheckEnforcement) {
    if (planCheckStatus.value.running > 0) {
      return t(
        "custom-approval.issue-review.disallow-approve-reason.some-task-checks-are-still-running"
      );
    }
    if (planCheckStatus.value.error > 0 || planCheckStatus.value.warning > 0) {
      return t(
        "custom-approval.issue-review.disallow-approve-reason.some-task-checks-didnt-pass"
      );
    }
  }

  // Show warnings for plan check results that don't violate enforcement policy
  if (
    planCheckEnforcement ===
    RolloutPolicy_Checkers_PlanCheckEnforcement.ERROR_ONLY
  ) {
    // Show warnings as non-blocking when ERROR_ONLY policy allows them
    if (planCheckStatus.value.warning > 0) {
      return t(
        "custom-approval.issue-review.disallow-approve-reason.some-task-checks-didnt-pass"
      );
    }
  }

  return undefined;
});

// Collect blocking validation errors
const validationErrors = computed(() => {
  const errors: string[] = [];

  // Permission errors - always block rollout
  if (!canRolloutTasks(eligibleTasks.value, issue.value)) {
    // Special message for data export issues when user is not the creator
    if (
      issue.value &&
      issue.value.type === Issue_Type.DATABASE_EXPORT &&
      issue.value.creator !== `${userNamePrefix}${currentUser.value.email}`
    ) {
      errors.push(t("task.data-export-creator-only"));
    } else {
      errors.push(t("task.no-permission"));
    }
  }

  // No active tasks to cancel - blocking error
  if (
    props.action === "CANCEL" &&
    eligibleTasks.value.length > 0 &&
    !eligibleTasks.value.some(
      (task) =>
        task.status === Task_Status.PENDING ||
        task.status === Task_Status.RUNNING
    )
  ) {
    errors.push(t("rollout.no-active-task-to-cancel"));
  }

  if (props.action === "RUN") {
    // No runnable tasks - blocking error
    if (
      eligibleTasks.value.length > 0 &&
      !eligibleTasks.value.some(
        (task) =>
          task.status === Task_Status.NOT_STARTED ||
          task.status === Task_Status.FAILED ||
          task.status === Task_Status.CANCELED
      )
    ) {
      errors.push(t("rollout.no-runnable-task"));
    }

    // Issue approval errors (only if policy requires it) - HARD BLOCK
    const requiresIssueApproval =
      rolloutPolicy.value?.policy?.case === "rolloutPolicy"
        ? rolloutPolicy.value.policy.value.checkers?.requiredIssueApproval
        : false;

    if (
      requiresIssueApproval &&
      issueApprovalStatus.value.hasIssue &&
      !issueApprovalStatus.value.rolloutReady
    ) {
      const isRejected = issueApprovalStatus.value.status === "rejected";
      errors.push(
        isRejected
          ? t("issue.approval.rejected-error")
          : t("issue.approval.pending-error")
      );
    }

    // Plan check errors (based on rollout policy) - HARD BLOCK
    if (planCheckError.value) {
      errors.push(planCheckError.value);
    }
  }

  return errors;
});

// Collect validation warnings that don't block rollout
const validationWarnings = computed(() => {
  const warnings: string[] = [];

  // Basic validation warnings
  if (eligibleTasks.value.length === 0) {
    warnings.push(t("common.no-data"));
  }

  if (props.action === "RUN") {
    // Validate scheduled time if not running immediately
    if (runTimeInMS.value !== undefined && runTimeInMS.value <= Date.now()) {
      warnings.push(t("task.error.scheduled-time-must-be-in-the-future"));
    }

    // Plan check warnings (non-blocking plan check results)
    if (planCheckWarning.value) {
      warnings.push(planCheckWarning.value);
    }

    // Issue approval warnings (when not required by policy but issue is not approved)
    const requiresIssueApproval =
      rolloutPolicy.value?.policy?.case === "rolloutPolicy"
        ? rolloutPolicy.value.policy.value.checkers?.requiredIssueApproval
        : false;

    if (
      !requiresIssueApproval &&
      issueApprovalStatus.value.hasIssue &&
      !issueApprovalStatus.value.rolloutReady
    ) {
      const isRejected = issueApprovalStatus.value.status === "rejected";
      warnings.push(
        isRejected
          ? t("issue.approval.rejected-error")
          : t("issue.approval.pending-error")
      );
    }

    // Automatic rollout info (always show as warning for non-export tasks with no task runs)
    if (
      isAutomaticRollout.value &&
      !isDatabaseExportTask.value &&
      !hasTaskRuns.value
    ) {
      warnings.push(t("rollout.automatic-rollout.description"));
    }
  }

  return warnings;
});

const shouldShowBypassOption = computed(() => {
  if (props.action !== "RUN") {
    return false;
  }

  // Only show bypass option when there are warnings but NO errors
  return (
    validationWarnings.value.length > 0 && validationErrors.value.length === 0
  );
});

// Extract stage from target
const targetStage = computed(() => {
  return props.target.stage;
});

// Get rollout policy for target stage environment
const { policy: rolloutPolicy } = usePolicyByParentAndType(
  computed(() => ({
    parentPath: targetStage.value?.environment || "",
    policyType: PolicyType.ROLLOUT_POLICY,
  }))
);

// Check if target stage has automatic rollout policy
const isAutomaticRollout = computed(() => {
  return (
    rolloutPolicy.value?.enforce &&
    rolloutPolicy.value.policy?.case === "rolloutPolicy" &&
    rolloutPolicy.value.policy.value.automatic
  );
});

// Check if any of the eligible tasks have task runs
const hasTaskRuns = computed(() => {
  return eligibleTasks.value.some((task) =>
    taskRuns.value.some((taskRun) => taskRun.name.startsWith(`${task.name}/`))
  );
});

// Extract tasks if provided directly
const targetTasks = computed(() => {
  if (props.target.type === "tasks") {
    return props.target.tasks;
  }
  return undefined;
});

const title = computed(() => {
  switch (props.action) {
    case "RUN":
      return t("task.run-task", { n: eligibleTasks.value.length });
    case "SKIP":
      return t("task.skip-task", { n: eligibleTasks.value.length });
    case "CANCEL":
      return t("task.cancel-task", { n: eligibleTasks.value.length });
    default:
      return "";
  }
});

// Get eligible tasks based on action type and target
const eligibleTasks = computed(() => {
  // For database create/export tasks, get all relevant tasks from all stages
  if (isDatabaseCreationOrExportTask.value) {
    const allTasks = flatten(rollout.value.stages.map((stage) => stage.tasks));
    // Apply action-based filtering
    if (props.action === "RUN") {
      return allTasks.filter(
        (task) =>
          task.status === Task_Status.NOT_STARTED ||
          task.status === Task_Status.CANCELED ||
          task.status === Task_Status.FAILED
      );
    } else if (props.action === "SKIP") {
      return allTasks.filter(
        (task) =>
          task.status === Task_Status.NOT_STARTED ||
          task.status === Task_Status.FAILED ||
          task.status === Task_Status.CANCELED
      );
    } else if (props.action === "CANCEL") {
      return allTasks.filter(
        (task) =>
          task.status === Task_Status.PENDING ||
          task.status === Task_Status.RUNNING
      );
    }
    return allTasks;
  }

  // If specific tasks are provided, use them
  if (
    props.target.type === "tasks" &&
    targetTasks.value &&
    targetTasks.value.length > 0
  ) {
    return targetTasks.value;
  }

  // Otherwise filter from stage tasks based on action
  const stageTasks = targetStage.value.tasks || [];

  if (props.action === "RUN") {
    return stageTasks.filter(
      (task) =>
        task.status === Task_Status.NOT_STARTED ||
        task.status === Task_Status.FAILED ||
        task.status === Task_Status.CANCELED
    );
  } else if (props.action === "SKIP") {
    return stageTasks.filter(
      (task) =>
        task.status === Task_Status.NOT_STARTED ||
        task.status === Task_Status.FAILED ||
        task.status === Task_Status.CANCELED
    );
  } else if (props.action === "CANCEL") {
    return stageTasks.filter(
      (task) =>
        task.status === Task_Status.PENDING ||
        task.status === Task_Status.RUNNING
    );
  }

  return [];
});

// Virtual scroll configuration
const useVirtualScroll = computed(() => eligibleTasks.value.length > 50);
const itemHeight = computed(() => 32); // Height of each task item in pixels

const showScheduledTimePicker = computed(() => {
  return props.action === "RUN";
});

// Check if any of the eligible tasks are database creation tasks
const isDatabaseCreationTask = computed(() => {
  const allTasks = flatten(rollout.value.stages.map((stage) => stage.tasks));
  return allTasks.every((task) => task.type === Task_Type.DATABASE_CREATE);
});

// Check if any of the eligible tasks are database export tasks
const isDatabaseExportTask = computed(() => {
  const allTasks = flatten(rollout.value.stages.map((stage) => stage.tasks));
  return allTasks.every((task) => task.type === Task_Type.DATABASE_EXPORT);
});

// Check if any of the eligible tasks are database creation or export tasks
const isDatabaseCreationOrExportTask = computed(() => {
  return isDatabaseCreationTask.value || isDatabaseExportTask.value;
});

const shouldShowStageInfo = computed(
  () => !isDatabaseCreationOrExportTask.value
);

const shouldShowTaskInfo = computed(() => !isDatabaseCreationTask.value);

// Handle execution mode change (immediate vs scheduled)
const handleExecutionModeChange = (value: string) => {
  if (value === "immediate") {
    runTimeInMS.value = undefined;
  } else {
    runTimeInMS.value = Date.now() + DEFAULT_RUN_DELAY_MS;
  }
};

// Helper function to group tasks by their stage (environment) for export tasks
const groupTasksByStage = (tasks: Task[]) => {
  const tasksByStage = new Map<string, Task[]>();
  for (const task of tasks) {
    const stageId = extractStageUID(task.name);
    if (!tasksByStage.has(stageId)) {
      tasksByStage.set(stageId, []);
    }
    tasksByStage.get(stageId)!.push(task);
  }
  return tasksByStage;
};

const addRunTimeToRequest = (request: BatchRunTasksRequest) => {
  if (runTimeInMS.value !== undefined) {
    // Convert timestamp to protobuf Timestamp format
    const runTimeSeconds = Math.floor(runTimeInMS.value / 1000);
    const runTimeNanos = (runTimeInMS.value % 1000) * 1000000;
    request.runTime = create(TimestampSchema, {
      seconds: BigInt(runTimeSeconds),
      nanos: runTimeNanos,
    });
  }
};

const handleConfirm = async () => {
  if (loading.value) return;

  loading.value = true;
  try {
    if (props.action === "RUN") {
      // For export tasks, group by stage/environment and make separate batch calls
      if (isDatabaseExportTask.value) {
        const tasksByStage = groupTasksByStage(eligibleTasks.value);

        // Make batch run calls for each stage/environment
        for (const [stageId, stageTasks] of tasksByStage) {
          const request = create(BatchRunTasksRequestSchema, {
            parent: `${rollout.value.name}/stages/${stageId}`,
            tasks: stageTasks.map((task) => task.name),
            reason: comment.value,
          });
          addRunTimeToRequest(request);
          await rolloutServiceClientConnect.batchRunTasks(request);
        }
      } else {
        // For non-export tasks, use the original logic
        const request = create(BatchRunTasksRequestSchema, {
          parent: targetStage.value.name,
          tasks: eligibleTasks.value.map((task) => task.name),
          reason: comment.value,
        });
        addRunTimeToRequest(request);
        await rolloutServiceClientConnect.batchRunTasks(request);
      }
    } else if (props.action === "SKIP") {
      // For export tasks, group by stage/environment and make separate batch calls
      if (isDatabaseExportTask.value) {
        const tasksByStage = groupTasksByStage(eligibleTasks.value);

        // Make batch skip calls for each stage/environment
        for (const [stageId, stageTasks] of tasksByStage) {
          const request = create(BatchSkipTasksRequestSchema, {
            parent: `${rollout.value.name}/stages/${stageId}`,
            tasks: stageTasks.map((task) => task.name),
            reason: comment.value,
          });
          await rolloutServiceClientConnect.batchSkipTasks(request);
        }
      } else {
        // For non-export tasks, use the original logic
        const request = create(BatchSkipTasksRequestSchema, {
          parent: targetStage.value.name,
          tasks: eligibleTasks.value.map((task) => task.name),
          reason: comment.value,
        });
        await rolloutServiceClientConnect.batchSkipTasks(request);
      }
    } else if (props.action === "CANCEL") {
      await cancelTasks();
    }

    emit("confirm");
  } catch (error) {
    pushNotification({
      module: "bytebase",
      style: "CRITICAL",
      title: t("common.error"),
      description: String(error),
    });
  } finally {
    loading.value = false;
    emit("close");
  }
};

const resetState = () => {
  comment.value = "";
  runTimeInMS.value = undefined;
  bypassPolicyChecks.value = false;
};

// initial the selected task run time.
watchEffect(() => {
  const runTimes = new Set(eligibleTasks.value.map((task) => task.runTime));
  if (runTimes.size != 1) {
    return;
  }
  const runTime = [...runTimes][0];
  if (!runTime) {
    return;
  }
  runTimeInMS.value = Number(runTime.seconds) * 1000 + runTime.nanos / 1000000;
});

const cancelTasks = async () => {
  // Group tasks by stage first
  const tasksByStage = new Map<string, Task[]>();
  for (const task of eligibleTasks.value) {
    // Extract stage name from task path: projects/{projectId}/rollouts/{rolloutId}/stages/{stageId}/tasks/...
    const [projectId, rolloutId, stageId] =
      getProjectIdRolloutUidStageUidTaskUid(task.name);
    if (projectId && rolloutId && stageId) {
      const stageName = `${projectNamePrefix}${projectId}/${rolloutNamePrefix}${rolloutId}/${stageNamePrefix}${stageId}`;
      if (!tasksByStage.has(stageName)) {
        tasksByStage.set(stageName, []);
      }
      tasksByStage.get(stageName)!.push(task);
    }
  }

  // Fetch task runs at stage level and filter by eligible tasks
  const cancelableTaskRunsByStage = new Map<string, string[]>();

  for (const [stageName, tasks] of tasksByStage) {
    const taskNames = new Set(tasks.map((t) => t.name));
    const request = create(ListTaskRunsRequestSchema, {
      parent: `${stageName}/tasks/-`,
    });

    const response = await rolloutServiceClientConnect.listTaskRuns(request);
    const stageTaskRuns = (response.taskRuns || [])
      .filter((taskRun) => {
        // Only include task runs for our eligible tasks
        const taskName = taskRun.name.split("/taskRuns/")[0];
        return (
          taskNames.has(taskName) &&
          (taskRun.status === TaskRun_Status.PENDING ||
            taskRun.status === TaskRun_Status.RUNNING)
        );
      })
      .map((taskRun) => taskRun.name);

    if (stageTaskRuns.length > 0) {
      cancelableTaskRunsByStage.set(stageName, stageTaskRuns);
    }
  }

  // Cancel task runs for each stage
  await Promise.all(
    Array.from(cancelableTaskRunsByStage.entries()).map(
      ([stageName, taskRunNames]) => {
        const request = create(BatchCancelTaskRunsRequestSchema, {
          parent: `${stageName}/tasks/-`,
          taskRuns: taskRunNames,
          reason: comment.value,
        });
        return rolloutServiceClientConnect.batchCancelTaskRuns(request);
      }
    )
  );
};
</script>
